#!/usr/bin/python3
# -*- coding: utf-8; tab-width: 4; indent-tabs-mode: t -*-

import os
import re
import sys
import elemlib
import subprocess
import strict_pgs
import strict_fsh
import strict_hdds
import strict_portage
import strict_hwcfg
from datetime import datetime
from fm_util import Util
from fm_param import FmConst
from stage4_gentoo import TargetFeatureFpemudOs
from helper_bbki import BbkiWrapper
from helper_swap import SwapManager
from helper_dyncfg import DynCfgModifier
from helper_pkg_warehouse import CloudOverlayDb
from helper_pkg_warehouse import EbuildRepositories
from helper_pkg_warehouse import EbuildOverlays
from helper_remote import RemoteOrLocalBuild


class FmMain:

    def __init__(self, param):
        self.param = param
        self.infoPrinter = self.param.infoPrinter

    def doShow(self):
        '''
        >>> Example:

        System status:
            unstable

        Main OS:
            Linux (kernel-x86_64-4.4.6)
        Auxillary OSes:
            None

        Storage layout:
            Name: efi-bcache-btrfs
            ESP partition: /dev/sdc1
            Cache partition: /dev/sdc2 (102.7GiB)
            HDDs: /dev/sda,bcache0 /dev/sdb,bcache16 (total: 8.2TiB)
        Swap:
            Disabled
        Logging:
            To harddisk (/var/log)

        Hardware:
            Unknown hardware
        Backend graphics devices:
            /dev/dri/card1 /dev/dri/card2 (total: 16GiB 14.3TFLOPs)
        Bluetooth:
            Enabled
        Printing:
            Enabled

        Users:
            fpemud, jaddy
        Session types:
            ACCESS    UI       BUSINESS         USABLE
            physical  gui      workstation      no
            physical  gui      ecenter          no

        Repositories:
            fpemud-overlay    [Dirty]       (Last Update: 2016-01-01 00:00:00)
            local             [Not Exist]

        Overlays:
            wrobel    [Subversion] (https://overlays.gentoo.org/svn/dev/wrobel     )

        Selected packages:
            app-admin/ansible               (repo-gentoo)
            app-misc/sway                   (overlay-uly55e5)

        Installed elements:
            game/myabandonware/ah64-apache-air-assault
        '''

        if self.param.runMode != "normal":
            print("WARNING: Running in \"%s\" mode!!!" % (self.param.runMode))
            print("")

        self.param.sysChecker.init(False, False)
        self.param.sysChecker.basicCheck(repositoryOverlayCheck=True)

        if self.param.runMode in ["normal", "setup"]:
            machine = strict_hwcfg.probe()
            layout = strict_hdds.get_storage_layout()
        elif self.param.runMode == "prepare":
            machine = None
            layout = None
        else:
            assert False
        bbkiObj = BbkiWrapper(machine, layout=layout)

        if self.param.runMode in ["normal", "setup"]:
            print("System status:")
            if bbkiObj.isStable():
                print("    stable")
            else:
                print("    unstable")
            print("")

        if self.param.runMode in ["normal", "setup"]:
            print("Main OS:")
            be = bbkiObj.getPendingBootEntry()
            if be is None:
                be = "None"
            else:
                be = "Linux (%s)" % (be.postfix)
            print("    %s" % (be))

            auxOsInfo = bbkiObj.getAuxOsInfo()
            if len(auxOsInfo) > 0:
                print("Auxillary OSes:")
                for item in auxOsInfo:
                    sys.stdout.write("    %s:" % (item.name))
                    for i in range(0, 20 - len(item.name)):
                        sys.stdout.write(" ")
                    print(item.partition_path)
        elif self.param.runMode == "prepare":
            print("Main OS:")
            be = bbkiObj.get_newest_boot_entry()
            if be is None:
                be = "None"
            else:
                be = "Linux (%s)" % (be.postfix)
            print("    %s" % (be))
        else:
            assert False
        print("")

        if self.param.runMode in ["normal", "setup"]:
            def __partSize(devpath):
                sz = Util.getBlkDevSize(devpath)
                return Util.formatSize(sz)

            print("Storage layout:")
            print("    Name: %s" % (layout.name))
            if layout.name == "bios-ext4":
                print("    Boot disk: %s" % (layout.boot_disk))
                print("    Root partititon: %s (%s)" % (layout.dev_rootfs, __partSize(layout.dev_rootfs)))
            elif layout.name == "efi-ext4":
                print("    Boot disk: %s" % (layout.boot_disk))
                print("    Root partititon: %s (%s)" % (layout.dev_rootfs, __partSize(layout.dev_rootfs)))
            elif layout.name in ["efi-btrfs"]:
                print("    Boot disk: %s" % (layout.boot_disk))
                totalSize = 0
                pvStrList = []
                for hddDevPath in layout.get_hdd_list():
                    parti = layout.get_hdd_data_partition(hddDevPath)
                    pvStrList.append("%s" % (parti))
                    totalSize += Util.getBlkDevSize(parti)
                print("    HDDs: %s (total: %s)" % (" ".join(pvStrList), Util.formatSize(totalSize)))
            elif layout.name == "efi-bcache-btrfs":
                if layout.get_ssd() is not None:
                    print("    SSD: %s (boot disk)" % (layout.get_ssd()))
                    print("    Cache partition: %s (%s)" % (layout.get_ssd_cache_partition(), __partSize(layout.get_ssd_cache_partition())))
                else:
                    print("    SSD: None")
                    print("    Boot disk: %s" % (layout.boot_disk))
                totalSize = 0
                pvStrList = []
                for hddDevPath in layout.get_hdd_list():
                    bcacheDevPath = layout.get_hdd_bcache_dev(hddDevPath)
                    pvStrList.append("%s,%s" % (hddDevPath, bcacheDevPath.replace("/dev/", "")))
                    totalSize += Util.getBlkDevSize(bcacheDevPath)
                print("    HDDs: %s (total: %s)" % (" ".join(pvStrList), Util.formatSize(totalSize)))
            elif layout.name == "efi-bcachefs":
                if layout.get_ssd() is not None:
                    print("    SSD: %s (boot disk)" % (layout.get_ssd()))
                    print("    Cache partition: %s (%s)" % (layout.get_ssd_cache_partition(), __partSize(layout.get_ssd_cache_partition())))
                else:
                    print("    SSD: None")
                    print("    Boot disk: %s" % (layout.boot_disk))
                totalSize = 0
                pvStrList = []
                for hddDevPath in layout.get_hdd_list():
                    parti = layout.get_hdd_data_partition(hddDevPath)
                    pvStrList.append("%s" % (parti))
                    totalSize += Util.getBlkDevSize(parti)
                print("    HDDs: %s (total: %s)" % (" ".join(pvStrList), Util.formatSize(totalSize)))
            else:
                assert False

            if self.param.runMode == "normal":
                print("Swap:")
                ret = SwapManager(layout, False).getInfo()
                if ret is not None:
                    bOnDiskOrInMemory, swapSize = ret
                    print("    Enabled (%s, %s)" % ("swap file" if bOnDiskOrInMemory else "zram", Util.formatSize(swapSize)))
                else:
                    print("    Disabled")

            # FIXME
            print("Logging:")
            if True:
                print("    To harddisk (/var/log)")

            print("")

        if self.param.runMode in ["normal", "setup"]:
            print("Hardware:")
            if machine.get_vendor() is not None:
                print("    %s %s" % (machine.get_vendor(), machine.get_model()))
            else:
                print("    DIY PC")

            ret = Util.findBackendGraphicsDevices()
            if len(ret) > 0:
                totalMem = 0
                totalFlopsForFp32 = 0
                for path in ret:
                    rc = Util.getVendorIdAndDeviceIdByDevNode(path)
                    if rc is None:
                        totalMem = None
                        break
                    info = DevHwInfoDb.getDevHwInfo(rc[0], rc[1])
                    if info is None:
                        totalMem = None
                        break
                    if "mem" not in info or not isinstance(info["mem"], int):
                        totalMem = None
                        break
                    if "fp32" not in info or not isinstance(info["fp32"], int):
                        totalMem = None
                        break
                    totalMem += info["mem"]
                    totalFlopsForFp32 += info["fp32"]

                totalStr = "unknown"
                if totalMem is not None:
                    totalStr = "%s %s" % (Util.formatSize(totalMem), Util.formatFlops(totalFlopsForFp32))

                print("Backend graphics devices:")
                print("    %s (total: %s)" % (" ".join(ret), totalStr))

            if "net-misc/networkmanager" in strict_portage.cfg.World().get_package_names():
                print("Network:")
                if "net-wireless/iwd" in strict_portage.cfg.World().get_package_names():
                    print("    Enabled (wired+wireless)")
                else:
                    print("    Enabled (wired)")

            if "net-wireless/bluez" in strict_portage.cfg.World().get_package_names():
                print("Bluetooth:")
                print("    Enabled")

            if "net-print/cups-meta" in strict_portage.cfg.World().get_package_names():
                print("Printing:")
                print("    Enabled")

            print("")

        if True:
            print("Users:")
            with strict_pgs.PasswdGroupShadow() as pgs:
                print("    %s" % (", ".join(pgs.getNormalUserList())))

            print("Session types:")
            print("    ACCESS    UI     BUSINESS       USABLE")
            useFlagList = Util.portageGetPkgEnabledUseFlags("app-admin/fpemud-os-gentoo-scripts")
            for access, ui, business in TargetFeatureFpemudOs.getSupportedSessionTypes():
                s = "    "
                s += access
                s += " " * (10 - len(access))
                s += ui
                s += " " * (7 - len(ui))
                s += business
                s += " " * (15 - len(business))
                s += "yes" if "session-%s-%s-%s" % (access, ui, business) in useFlagList else "no"
                print(s)

            print("")

        print("Repositories:")
        repoman = EbuildRepositories()
        repoList = repoman.getRepositoryList()
        if len(repoList) > 0:
            maxLen = Util.strListMaxLen(repoList)
            for repoName in repoList:
                s1 = Util.pad(repoName, maxLen)
                if repoman.isRepoExist(repoName):
                    print("    %s [Good     ] Last Update: %s" % (s1, Util.getDirLastUpdateTime(repoman.getRepoDir(repoName))))
                else:
                    print("    %s [Not Exist]" % (s1))
        else:
            print("    None")
        print("")

        print("Overlays:")
        layman = EbuildOverlays()
        overlayList = layman.getOverlayList()
        if len(overlayList) > 0:
            tlist2 = []
            tlist3 = []
            for lname in overlayList:
                if layman.getOverlayType(lname) == "static":
                    ltype = "Static"
                    lurl = ""
                else:
                    ltype, lurl = layman.getOverlayVcsTypeAndUrl(lname)
                    if ltype == "git":
                        ltype = "Git"
                    elif ltype == "svn":
                        ltype = "Subversion"
                    else:
                        assert False
                tlist2.append(ltype)
                tlist3.append(lurl)
            maxLen1 = Util.strListMaxLen(overlayList)
            maxLen2 = Util.strListMaxLen(tlist2)
            maxLen3 = Util.strListMaxLen(tlist3)
            for i in range(0, len(overlayList)):
                s1 = Util.pad(overlayList[i], maxLen1)
                s2 = Util.pad(tlist2[i], maxLen2)
                s3 = Util.pad(tlist3[i], maxLen3)
                print("    %s [%s %s] Last Update: %s" % (s1, s2, s3, Util.getDirLastUpdateTime(layman.getOverlayDir(overlayList[i]))))
        else:
            print("    None")
        print("")

        print("Selected packages:")
        if True:
            pkgList = strict_portage.cfg.World().get_package_names()
            maxLen = max([len(x) for x in pkgList])

            for repoName in repoman.getRepositoryList():
                tempList = []
                for pkg in pkgList:
                    if os.path.exists(os.path.join(repoman.getRepoDir(repoName), pkg)):
                        print("    %s (repo-%s)" % (Util.pad(pkg, maxLen), repoName))
                    else:
                        tempList.append(pkg)
                pkgList = tempList

            for overlayName in layman.getOverlayList():
                tempList = []
                for pkg in pkgList:
                    if os.path.exists(os.path.join(layman.getOverlayDir(overlayName), pkg)):
                        print("    %s (overlay-%s)" % (Util.pad(pkg, maxLen), overlayName))
                    else:
                        tempList.append(pkg)
                pkgList = tempList

            for pkg in pkgList:
                print("    %s" % (pkg))

        print("Installed elements:")
        with elemlib.Library(system_library_or_user_library=True) as locStor:
            for e in locStor.get_cached_elements("*/*/*"):
                print("    %s" % (e.id))

        return 0

    def doInitialize(self):
        if self.param.runMode == "normal":
            return self._die("Operation is not supported in \"%s\" mode." % (self.param.runMode))

        if self.param.runMode == "setup":
            self.param.sysChecker.init(False, False)
            self.param.sysChecker.basicCheck(repositoryOverlayCheck=True)
            machine = strict_hwcfg.probe()
            layout = strict_hdds.get_storage_layout()
            bbkiObj = BbkiWrapper(machine, layout=layout)
            with strict_hdds.BootDirWriter(layout):
                bbkiObj.updateInitramfs()
                bbkiObj.updateBootloader()
            return 0

        if self.param.runMode == "prepare":
            self.param.sysChecker.init(True, True)
            self.param.sysChecker.basicCheck(repositoryOverlayCheck=True)
            return 0

        assert False

    def doUpdate(self, bUseCache):
        self.param.sysUpdater.update(not bUseCache)
        return 0

    def doClean(self, bPretend):
        self.param.sysCleaner.clean(bPretend)
        return 0

    def doCheck(self, bAutoFix, bMoreHardware, bMoreFs):
        if self.param.runMode not in ["normal", "setup"]:
            return self._die("Operation is not supported in \"%s\" mode." % (self.param.runMode))

        self.param.sysChecker.init(False, bAutoFix)
        self.param.sysChecker.fullCheck(bMoreHardware, bMoreFs)
        return 0

    def doStablize(self):
        if self.param.runMode != "normal":
            return self._die("Operation is not supported in \"%s\" mode." % (self.param.runMode))

        self.param.sysChecker.init(False, False)
        self.param.sysChecker.basicCheck(repositoryOverlayCheck=True)

        self.param.sysUpdater.stablize()

        return 0

    def doHddAdd(self, devPath):
        if self.param.runMode not in ["normal", "setup"]:
            return self._die("Operation is not supported in \"%s\" mode." % (self.param.runMode))

        self.infoPrinter.printInfo(">> Preparing...")
        machine = None
        layout = None
        if True:
            self.param.sysChecker.init(False, False)
            self.param.sysChecker.basicCheck()

            machine = strict_hwcfg.probe()

            layout = strict_hdds.get_storage_layout()
            if layout is None:
                return self._die("Invalid storage layout.")

            if layout.name in ["bios-ext4", "efi-ext4"]:
                return self._die("Storage layout \"%s\" does not support this operation." % (layout.name))
            assert layout.name in ["efi-btrfs", "efi-bcache-btrfs", "efi-bcachefs"]

            print("")

        self.infoPrinter.printInfo(">> Adding harddisk...")
        if True:
            c = Util.hwcfgGetComponentByUdevDeviceNode(machine, strict_hwcfg.ComponentType.HARDDISK, devPath)
            if c is None:
                return self._die("Harddisk \"%s\" has no hardware configuration" % (devPath))

            if c.get_write_safety() == "safe":
                pass
            elif c.get_write_safety() == "safe-uncached":
                # FIXME: currently we don't deal with it yet
                # Util.setBlkDevHardwareWriteCacheStatus(devPath, False)
                pass
            elif c.get_write_safety() == "unsafe":
                pass
            else:
                assert False
            layout.add_disk(devPath)
            self.param.sysUpdater.updateAfterHddAddOrRemove(layout, BbkiWrapper(machine, layout=layout))
            print("")

        return 0

    def doHddRemove(self, devPath):
        if self.param.runMode not in ["normal", "setup"]:
            return self._die("Operation is not supported in \"%s\" mode." % (self.param.runMode))

        self.infoPrinter.printInfo(">> Preparing...")
        machine = None
        layout = None
        if True:
            self.param.sysChecker.init(False, False)
            self.param.sysChecker.basicCheck()

            machine = strict_hwcfg.probe()

            layout = strict_hdds.get_storage_layout()
            if layout is None:
                return self._die("Invalid storage layout.")

            if layout.name in ["bios-ext4", "efi-ext4"]:
                return self._die("Storage layout \"%s\" does not support this operation." % (layout.name))
            assert layout.name in ["efi-btrfs", "efi-bcache-btrfs", "efi-bcachefs"]

            print("")

        self.infoPrinter.printInfo(">> Removing harddisk...")
        if True:
            layout.remove_disk(devPath)
            self.param.sysUpdater.updateAfterHddAddOrRemove(layout, BbkiWrapper(machine, layout=layout))
            print("")

        return 0

    def doAddUser(self, username, bWheel):
        self.param.sysChecker.init(False, False)
        self.param.sysChecker.basicCheck()

        passwd = self.param.userManager.addUser(username)
        if bWheel:
            self.param.userManager.addUserToGroup(username, "wheel")
        print("User added. username: %s, password: %s" % (username, passwd))
        return 0

    def doRemoveUser(self, username):
        self.param.sysChecker.init(False, False)
        self.param.sysChecker.basicCheck()

        self.param.userManager.removeUser(username)
        return 0

    def doResetUserPassword(self, username):
        self.param.sysChecker.init(False, False)
        self.param.sysChecker.basicCheck()

        self.param.userManager.resetUserPassword(username)
        return 0

    def doFlushUser(self, username):
        self.param.sysChecker.init(False, False)
        self.param.sysChecker.basicCheck()

        self.param.userManager.flushUser(username)
        return 0

    def doRemoveOverlay(self, overlayName):
        self.param.sysChecker.init(False, False)
        self.param.sysChecker.basicCheck(repositoryOverlayCheck=True)

        layman = EbuildOverlays()
        if not layman.isOverlayExist(overlayName):
            return self._die("Overlay \"%s\" is not installed." % (overlayName))

        overlayType = None
        try:
            overlayType = layman.getOverlayType(overlayName)
        except BaseException:
            # allow removing corrupted overlay
            overlayType = "trusted"
        if overlayType == "static":
            return self._die("Overlay \"%s\" is a static overlay." % (overlayName))

        layman.removeOverlay(overlayName)
        return 0

    def doDisableOverlayPkg(self, overlayName, pkgName):
        self.param.sysChecker.init(False, False)
        self.param.sysChecker.basicCheck(repositoryOverlayCheck=True)

        layman = EbuildOverlays()

        if not layman.isOverlayExist(overlayName):
            return self._die("Overlay \"%s\" is not installed." % (overlayName))
        if layman.getOverlayType(overlayName) != "transient":
            return self._die("Overlay \"%s\" is not a transient overlay." % (overlayName))

        layman.disableOverlayPackage(overlayName, pkgName)
        return 0

    def installPackage(self, pkgName, bTest, overlayName, overlayVcsType, overlayUrl):
        assert (overlayVcsType is None and overlayUrl is None) or (overlayName is not None and overlayVcsType is not None and overlayUrl is not None)

        if overlayName is not None and "/" not in pkgName:
            return self._die("Category not specified when installing package from overlay.")

        # modify dynamic config
        self.infoPrinter.printInfo(">> Preparing...")
        layman = None
        if True:
            self.param.sysChecker.init(False, False)
            self.param.sysChecker.basicCheck(repositoryOverlayCheck=True)
            DynCfgModifier.update()
            layman = EbuildOverlays()
            print("")

        # add overlay
        if overlayName is not None and not layman.isOverlayExist(overlayName):
            # update overlay database
            self.infoPrinter.printInfo(">> Updating overlay database...")
            overlayDb = CloudOverlayDb()
            overlayDb.update()
            print("")

            # add overlay
            if overlayVcsType is None:
                if not overlayDb.hasOverlay(overlayName):
                    return self._die("Overlay \"%s\" is not in overlay database, --vcs-type and --url must be specified." % (overlayName))
                overlayVcsType, overlayUrl = overlayDb.getOverlayVcsTypeAndUrl(overlayName)
                self.infoPrinter.printInfo(">> Adding %s overlay \"%s\" from \"%s\"..." % (overlayVcsType, overlayName, overlayUrl))
            else:
                self.infoPrinter.printInfo(">> Adding overlay...")
            layman.addTransientOverlay(overlayName, overlayVcsType, overlayUrl)
            print("")

        # enable overlay package
        # FIXME: should enable deps
        if overlayName is not None and layman.getOverlayType(overlayName) == "transient":
            self.infoPrinter.printInfo(">> Enable package \"%s\" in overlay \"%s\"..." % (pkgName, overlayName))
            layman.enableOverlayPackage(overlayName, pkgName)
            print("")

        # emerge package
        with RemoteOrLocalBuild(self.infoPrinter) as obj:
            with obj.getExecContext(syncDownType="system") as ctx:
                self.infoPrinter.printInfo(">> Installing %s..." % (pkgName))
                ctx.exec(os.path.join(FmConst.libexecDir, "op-emerge-package.py"),
                         pkgName,
                         Util.boolToStr01(bTest))

        return 0

    def uninstallPackage(self, packageName):
        self.param.sysChecker.init(False, False)
        self.param.sysChecker.basicCheck(repositoryOverlayCheck=True)

        subprocess.check_call(["emerge", "-C", packageName])
        return 0

    def installElement(self, elementId):
        self.param.sysChecker.init(False, False)
        self.param.sysChecker.basicCheck(repositoryOverlayCheck=True)

        with elemlib.Library(system_library_or_user_library=True) as locStor:
            locStor.cache_element(elementId)
        return 0

    def uninstallElement(self, elementId):
        self.param.sysChecker.init(False, False)
        self.param.sysChecker.basicCheck(repositoryOverlayCheck=True)

        with elemlib.Library(system_library_or_user_library=True) as locStor:
            locStor.remove_cached_element(elementId)
        return 0

    def buildAssistDisk(self, devPath, bWithStage4, bUseCache):
        if self.param.runMode not in ["normal"]:
            return self._die("Operation is not supported in \"%s\" mode." % (self.param.runMode))

        self.param.otherHostManager.buildAssistDisk(devPath, bWithStage4, bUseCache)
        return 0

    def newHost(self, osType, hostname, devPath, bUseCache):
        if self.param.runMode not in ["normal"]:
            return self._die("Operation is not supported in \"%s\" mode." % (self.param.runMode))

        self.param.otherHostManager.newHost(osType, hostname, devPath, bUseCache)
        return 0

    def backup(self, devPath):
        if re.fullmatch("/dev/[a-z]+", devPath) is None:
            raise Exception("invalid backup device %s" % (devPath))

        parti = devPath + "1"
        if os.path.exists(parti):
            if Util.isMountPoint(parti):
                raise Exception("%s is already mounted" % (devPath))

            if Util.getBlkDevPartitionTableType(devPath) != "gpt":
                raise Exception("invalid backup device %s" % (devPath))

            # python-magic is not able to identify "parti" as "application/x-squashfs"
            out = subprocess.check_output(["file", "-b", "-s", parti], text=True)
            if not (out.startswith("Squashfs filesystem,") or Util.isFileStartWithAllZero(parti, 1024)):
                raise Exception("invalid backup device %s" % (devPath))

            if os.path.exists(devPath + "2"):
                raise Exception("invalid backup device %s" % (devPath))

        self.infoPrinter.printInfo(">> Preparing...")
        if True:
            self.param.sysChecker.init(False, False)
            self.param.sysChecker.basicCheck()
            print("")

        self.infoPrinter.printInfo(">> Doing backup...")
        if True:
            if not os.path.exists(parti):
                Util.formatDisk(devPath, "gpt")

            obj = strict_fsh.RootFs()
            wildcards = []
            if True:
                wildcards = strict_fsh.merge_wildcards(wildcards, obj.get_wildcards(wildcards_flag=strict_fsh.WILDCARDS_SYSTEM_CFG))
                # wildcards = strict_fsh.merge_wildcards(wildcards, obj.get_wildcards(wildcards_flag=strict_fsh.WILDCARDS_SYSTEM_DATA))
                wildcards = strict_fsh.merge_wildcards(wildcards, obj.get_wildcards(wildcards_flag=strict_fsh.WILDCARDS_USER))
                wildcards = strict_fsh.deduct_wildcards(wildcards, obj.get_wildcards(wildcards_flag=strict_fsh.WILDCARDS_USER_CACHE))
                wildcards = strict_fsh.deduct_wildcards(wildcards, obj.get_wildcards(wildcards_flag=strict_fsh.WILDCARDS_USER_TRASH))
                # FIXME:
                wildcards = strict_fsh.deduct_wildcards(wildcards, ["+ /home/fpemud/Downloads/***"])
                wildcards = strict_fsh.deduct_wildcards(wildcards, ["+ /home/fpemud/parti-garbage/***"])
                wildcards = strict_fsh.deduct_wildcards(wildcards, ["+ /home/fpemud/test*/***"])
                wildcards = strict_fsh.deduct_wildcards(wildcards, ["+ /home/fpemud/workspace/***"])
                wildcards = strict_fsh.deduct_wildcards(wildcards, ["+ /home/fpemud/codes/***"])
                wildcards = strict_fsh.deduct_wildcards(wildcards, ["+ /home/fpemud/maint/***"])
                wildcards = strict_fsh.deduct_wildcards(wildcards, ["+ /home/fpemud/.local/share/**"])
                wildcards = strict_fsh.deduct_wildcards(wildcards, ["+ /home/fpemud/zhdd/***"])
                wildcards = strict_fsh.deduct_wildcards(wildcards, ["+ /home/jaddy/**"])
                wildcards = strict_fsh.deduct_wildcards(wildcards, ["+ /root/**"])
            fileList = obj.wildcards_glob(wildcards)

            totalSize = 0
            for fullfn in fileList:
                try:
                    totalSize += os.path.getsize(fullfn)
                except FileNotFoundError:
                    # for broken symlink
                    pass

            inStr = ""
            for line in fileList:
                inStr += "%s\n" % (line)
            subprocess.run("tar -c --no-recursion --absolute-names -f - -T - | pv -s %d | tar2sqfs -q -f %s" % (totalSize, parti), input=inStr, shell=True, text=True, check=True)

            Util.gptSetPartitionLabel(parti, datetime.now().strftime("Fpemud OS Backup %Y-%m-%d %H:%M:%S"))

    def restore(self, devPath):
        assert False

    def _die(self, msg):
        print(msg, file=sys.stderr)
        return 1
